package lintulaskenta.rekisteri;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import lintulaskenta.entity.Havainnointitapahtuma;
import lintulaskenta.entity.Havaintopiste;
import lintulaskenta.entity.Lajihavainto;
import lintulaskenta.entity.Parvihavainto;
import lintulaskenta.entity.Pistevuosi;
import lintulaskenta.entity.Reitti;

/**
 *
 * @author amjaalto
 *
 *
 * Rajapinta kannan käyttöön, tarjoaa lisäyksen ja muokkauksen (merge-operaatio),
 * sekä kannasta haun. Haku tapahtuu asettamalla hakuehtoja. Jokaiselle taululle
 * on oma metodinsa ehtojen asettamiseen. Kun ehdot on asetettu suoritetaan
 * hakumetodi.
 *
 */
public class ReittiRekisteri extends Rekisteri {

    ArrayList<Predicate> predikaattilista;
    EntityManager entitymanager;
    CriteriaBuilder criteriabuilder;
    CriteriaQuery<Reitti> criteriaquery;
    Root<Reitti> reitti;

    public ReittiRekisteri() {
        entitymanager = getEntityManager();
        criteriabuilder = entitymanager.getCriteriaBuilder();
        criteriaquery = criteriabuilder.createQuery(Reitti.class);
        reitti = criteriaquery.from(Reitti.class);
        criteriaquery.select(reitti);
        predikaattilista = new ArrayList();
    }

    public void asetaReitinHakuehdot(Map<String, Object> ehdot) {
        for (String attribuutinNimi : ehdot.keySet()) {
            predikaattilista.add(criteriabuilder.equal(reitti.get(attribuutinNimi), ehdot.get(attribuutinNimi)));
        }
    }

    public void asetaReitinHavaintotapahtumienHakuehdot(Map<String, Object> ehdot) {
        Join<Reitti, Havainnointitapahtuma> tapahtumat = reitti.join("havainnointitapahtumat");
        for (String attribuutinNimi : ehdot.keySet()) {
            predikaattilista.add(criteriabuilder.equal(tapahtumat.get(attribuutinNimi), ehdot.get(attribuutinNimi)));
        }
    }

    public void asetaReitinPistevuosienHakuehdot(Map<String, Object> ehdot) {
        Join<Reitti, Havaintopiste> pisteet = reitti.join("havaintopisteet");
        Join<Havaintopiste, Pistevuosi> pvt = pisteet.join("pistevuodet");
//        Join<Reitti, Pistevuosi> pvt = reitti.join("havaintopisteet").join("pistevuodet");
        predikaattilista.add(criteriabuilder.notEqual(pisteet.get("havaintopisteId"), 0));
        for (String attribuutinNimi : ehdot.keySet()) {
            predikaattilista.add(criteriabuilder.equal(pvt.get(attribuutinNimi), ehdot.get(attribuutinNimi)));
        }
    }


    public void asetaReitinLajihavaintojenHakuehdot(Map<String, Object> ehdot) {
        Join<Reitti, Lajihavainto> lajit = reitti.join("havaintopisteet").join("pistevuodet").join("lajihavainnot");
        for (String attribuutinNimi : ehdot.keySet()) {
            predikaattilista.add(criteriabuilder.equal(lajit.get(attribuutinNimi), ehdot.get(attribuutinNimi)));
        }
    }
    public List<Reitti> suoritaHaku() {
        Predicate[] predikaatit = new Predicate[predikaattilista.size()];
        int index = 0;
        for (Predicate predikaatti : predikaattilista) {
            predikaatit[index] = predikaatti;
            index++;
        }
        criteriaquery.where(predikaatit);
        criteriaquery.distinct(true);
        return entitymanager.createQuery(criteriaquery).getResultList();
    }

    public Reitti getReittiReittinumerolla(long reittinro) {
        try {
            EntityManager em = getEntityManager();
            Query query = em.createQuery("SELECT r FROM Reitti r WHERE r.vanhaReittiNumero = :reittinro");
            query.setParameter("reittinro", reittinro);
            return (Reitti) query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    public List<Reitti> haeReititEhdoilla(Map<String, Object> ehdot) throws Exception {
        EntityManager em = getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Reitti> cq = cb.createQuery(Reitti.class);

        // luodaan kyselylle juuri
        Root<Reitti> juuri = cq.from(Reitti.class);

        // SELECT reitti FROM Reitti
        cq.select(juuri);

        // luodaan reittiehdot parametrina saadusta mapista
        Predicate[] predikaatit = new Predicate[ehdot.size()];
        int index = 0;
        for (String attribuutinNimi : ehdot.keySet()) {
            //haetaan juurelta mapin avainta vastaava attribuutti (avaimet nimetty Reitin attribuuttien mukaan)
            //asetetaan hakuehdoksi että Reitin attribuutti vastaa mapin avainta vastaavaa arvoa
            //(vastaa esim tätä: laskentakunta.equals("DRAGSF"), jolloin mapin avain on "laskentakunta" ja sitä vastaa arvo "DRAGSF")
            predikaatit[index] = cb.equal(juuri.get(attribuutinNimi), ehdot.get(attribuutinNimi));
            index++;
        }
        //tehdään WHERE kysely näillä predikaateilla (ehdoilla)
        cq.where(predikaatit);
        //palautetaan ehdoilla löydetyt Reitit listana
        return em.createQuery(cq).getResultList();
    }

    /*
     * Tällä voi hakea reittejä joita on laskettu jonakin tiettynä vuonna.
     */
    public List<Reitti> haeReititTapahtumanEhdoilla(Map<String, Object> ehdot) throws Exception {
        EntityManager em = getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Reitti> cq = cb.createQuery(Reitti.class);

        Root<Reitti> juuri = cq.from(Reitti.class);
        Join<Reitti, Havainnointitapahtuma> tapahtumat = juuri.join("havainnointitapahtumat");
        cq.select(juuri);

        Predicate[] predikaatit = new Predicate[ehdot.size()];
        int index = 0;
        for (String attribuutinNimi : ehdot.keySet()) {
            predikaatit[index] = cb.equal(tapahtumat.get(attribuutinNimi), ehdot.get(attribuutinNimi));
            index++;
        }
        cq.where(predikaatit);

        return em.createQuery(cq).getResultList();
    }

    /*
     * Tällä voi hakea reittejä asettaen rajoituksia reitin sekä siihen liittyvien havainnointitapahtumien
     * attribuuteille.
     */
    public List<Reitti> haeReititReitinJaTapahtumanEhdoilla(Map<String, Object> reittiehdot, Map<String, Object> tapahtumaehdot) throws Exception {
        EntityManager em = getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Reitti> cq = cb.createQuery(Reitti.class);

        Root<Reitti> juuri = cq.from(Reitti.class);
        Join<Reitti, Havainnointitapahtuma> tapahtumat = juuri.join("havainnointitapahtumat");
        cq.select(juuri);

        Predicate[] predikaatit = new Predicate[reittiehdot.size() + tapahtumaehdot.size()];
        int index = 0;
        for (String attribuutinNimi : reittiehdot.keySet()) {
            predikaatit[index] = cb.equal(juuri.get(attribuutinNimi), reittiehdot.get(attribuutinNimi));
            index++;
        }
        for (String attribuutinNimi : tapahtumaehdot.keySet()) {
            predikaatit[index] = cb.equal(tapahtumat.get(attribuutinNimi), tapahtumaehdot.get(attribuutinNimi));
            index++;
        }

        cq.where(predikaatit);

        return em.createQuery(cq).getResultList();
    }

    public List<Reitti> haeReititKoordinaattienValista(int alarajaItakoord,
            int ylarajaItakoord,
            int alarajaPohjkoord,
            int ylarajaPohjkoord)
            throws Exception {
        EntityManager em = getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Reitti> cq = cb.createQuery(Reitti.class);

        Root<Reitti> juuri = cq.from(Reitti.class);

        cq.select(juuri);

        ParameterExpression<Integer> alaIta = cb.parameter(Integer.class);
        ParameterExpression<Integer> ylaIta = cb.parameter(Integer.class);
        ParameterExpression<Integer> alaPoh = cb.parameter(Integer.class);
        ParameterExpression<Integer> ylaPoh = cb.parameter(Integer.class);

        Predicate predikaatti1 = cb.between(juuri.get("itakoordinaatti").as(Integer.class), alaIta, ylaIta);
        Predicate predikaatti2 = cb.between(juuri.get("pohjoiskoordinaatti").as(Integer.class), alaPoh, ylaPoh);

        cq.where(predikaatti1, predikaatti2);

        TypedQuery<Reitti> query = em.createQuery(cq);
        query.setParameter(alaIta, alarajaItakoord);
        query.setParameter(ylaIta, ylarajaItakoord);
        query.setParameter(alaPoh, alarajaPohjkoord);
        query.setParameter(ylaPoh, ylarajaPohjkoord);

        return query.getResultList();
    }

    public void poistaReittiJaKaikkiSiihenLiittyva(long id) {
        EntityManager em = getEntityManager();
        em.getTransaction().begin();
        Reitti r = em.find(Reitti.class, id);
        em.remove(r);
        em.getTransaction().commit();
    }

    public static void main(String[] args) throws Exception {
        ReittiRekisteri rr = new ReittiRekisteri();
        System.out.println("Start");

        // halutaan reitit joiden attribuutti vanhaReittiNumero on 5001
        // ja laskentakunta on "DRAGSF"
        Map<String, Object> reittiehdot = new HashMap<String, Object>();
        Map<String, Object> havaintoehdot = new HashMap<String, Object>();
        Map<String, Object> pistevuosiehdot = new HashMap<String, Object>();
        Map<String, Object> lajihavaintoehdot = new HashMap<String, Object>();
        reittiehdot.put("vanhaReittiNumero", 5167);
//        reittiehdot.put("talvireitinNumero", 0); //reitin reittiehdot
//        havaintoehdot.put("vuosi", 1984);
//        lajihavaintoehdot.put("parvilaji", "ANTTRI");
//        pistevuosiehdot.put("biotooppikoodiNyt", 17);
//        havaintoehdot.put("vuosi", 1990);
        rr.asetaReitinHakuehdot(reittiehdot);
//        rr.asetaReitinHavaintotapahtumienHakuehdot(havaintoehdot);
//        rr.asetaReitinLajihavaintojenHakuehdot(lajihavaintoehdot);
//        rr.asetaReitinPistevuosienHakuehdot(pistevuosiehdot);
        long alku = System.currentTimeMillis();

        List<Reitti> reitit = rr.suoritaHaku();//haeReititReitinJaTapahtumanEhdoilla(reittiehdot, havaintoehdot);
//        List<Reitti> reitit = rr.haeReititEhdoilla(reittiehdot);

//        rr.poistaReittiJaKaikkiSiihenLiittyva(reitit.get(0).getReitinNumero());
//        System.out.println("Print"); //50901987
//        int havainnot = 0;
        System.out.println("Total " + reitit.size() + " reittiä.");
        for (Reitti r : reitit) {
            System.out.println("Reitti: " + r.getVanhaReittiNumero() + " ----------------");
            System.out.println("Reitillä on pisteitä yht. " + r.getHavaintopisteet().size() + ".");
            for (Havainnointitapahtuma ht : r.getHavainnointitapahtumat())
                System.out.println("Tapahtuma vuonna " + ht.getVuosi() + " kunnassa " + ht.getLaskentakunta() +" lähempi paikka on " + ht.getLahempiPaikka());
//            havainnot += r.getHavainnointitapahtumat().size();
            for (Havaintopiste h : r.getHavaintopisteet()) {
                System.out.println("Havaintopiste: " + h.getHpId() + " ****************");
                System.out.println("Pisteellä vuosi-instansseja: " + h.getPistevuodet().size() + ".");
                for (Pistevuosi pv : h.getPistevuodet()) {
//                    if (pv.getReitinSade() == 25) {
//                        System.out.println("Reitti: " + r.getVanhaReittiNumero() + " ----------------");
//                        System.out.println("Havaintopiste: " + h.getHavaintopisteId() + " ****************");
//                        System.out.println("Pisteellä vuosi-instansseja: " + h.getPistevuodet().size() + ".");
                    System.out.println("Vuotena: " + pv.getVuosi() + " pisteen numero: " + pv.getPisteenJarjestysnumero() + " pisteen biotooppi " + pv.getBiotooppikoodiNyt() + " pisteellä lajihavaintoja: " + pv.getLajihavainnot().size());
//                    }
//                    if (pv.getVuosi() == 2005)
                    for (Lajihavainto lh : pv.getLajihavainnot()) {
                        System.out.print(" " + lh.getParvilaji() + " sisäpuolella: " + lh.getParimaaraSisapuolella() + " ulkopuolella: " + lh.getParimaaraUlkopuolella() + ". ");
                    }
                    for (Parvihavainto ph : pv.getParvihavainnot()) {
                        System.out.print("Parvihavainto: " + ph.getParvilaji() + " parven koko : " + ph.getParvenKoko() + ". ");
                    }
                    System.out.println("");
                }
            }
        }
        long loppu = System.currentTimeMillis();
        System.out.println("Finish. Aikaa meni: " + (loppu - alku));
//        System.out.println("Havainnointitapahtumia yht. "+havainnot);

    }
}
